﻿using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using IndieStudio.BugsBang.Utility;

#if UNITY_EDITOR
using UnityEditor;
#endif
using UnityEngine.SceneManagement;

///Developed By Indie Studio
///https://assetstore.unity.com/publishers/9268
///www.indiestd.com
///info@indiestd.com

namespace IndieStudio.BugsBang.Game
{
	[DisallowMultipleComponent]
	[ExecuteInEditMode]
	public class Path : MonoBehaviour
	{
		/// <summary>
		/// The points list of the path.
		/// </summary>
		public List<Transform> points = new List<Transform> ();

		/// <summary>
		/// Used for Unity Editor to toggle the contents.
		/// </summary>
		[HideInInspector]
		public bool showContents = true;

		/// <summary>
		/// Wehther to run the instantiate process on start or not.
		/// </summary>
		public bool runOnStart;

		/// <summary>
		/// Whether to destory the objects of the path on reach the end point or not.
		/// </summary>
		public bool destoryObjectsOnReachEnd = true;

		/// <summary>
		/// The instantiate mode.
		/// </summary>
		public InstantiateMode instantiateMode = InstantiateMode.LOOP;

		/// <summary>
		/// The bezier curve points.
		/// </summary>
		private List<Vector3> bezierPoints = new List<Vector3> ();

		/// <summary>
		/// The delay time before insantiate.
		/// </summary>
		[Range (0.1f, 50)]
		public float delayBeforeInsantiate = 0;

		/// <summary>
		/// The wave delay time.
		/// </summary>
		[Range (0.1f, 50)]
		public float waveDelayTime = 3f;

		/// <summary>
		/// The wave item delay time.
		/// </summary>
		[Range (0.1f, 50)]
		public float waveItemDelayTime = 1f;

		/// <summary>
		/// The speed of the objects.
		/// </summary>
		[Range (0.1f, 10)]
		public float objectsSpeed = 1f;

		/// <summary>
		/// The speed of the animator of the objects.
		/// </summary>
		[Range (0.1f, 5)]
		public float objectsAnimatorSpeed = 1f;

		/// <summary>
		/// The scale of the objects in the world space.
		/// </summary>
		public Vector2 objectsScale = Vector2.one;

		/// <summary>
		/// The list of path objects.
		/// </summary>
		public List<PathObject> pathObjects;

		/// <summary>
		/// The trail effect for path objects.
		/// </summary>
		public GameObject trailEffect;

		/// <summary>
		/// The bezier instance.
		/// </summary>
		private static Bezier bezier = new Bezier ();

		/// <summary>
		/// Whether the instantiate process is paused or not.
		/// </summary>
		private bool isPaused;

		/// <summary>
		/// This flag enables/disables the swapping process of the objects between the paths in the scene.
		/// </summary>
		[HideInInspector]
		public bool allowSwap;

		void Awake ()
		{
			CalculateBezierPoints ();
		}

		void Start ()
		{
			if (runOnStart) {
				Run ();
			}
		}

		/// <summary>
		/// The run/instantiate process.
		/// </summary>
		public void Run ()
		{
			if (Application.isPlaying) {
				StartCoroutine ("RunCourtine");
			}
		}

		/// <summary>
		/// The run courtine.
		/// </summary>
		/// <returns>The courtine.</returns>
		private IEnumerator RunCourtine ()
		{
			yield return new WaitForSeconds (delayBeforeInsantiate);

			bool isRunning = true;

			while (isRunning) {

				isRunning = instantiateMode == InstantiateMode.LOOP ? true : false;

				if (LevelsManager.instance.GetCurrentLevel ().randomMode && allowSwap) {
					Paths.instance.SwapPathObjects(this);
				}

				//Create the objects of the path
				foreach (PathObject pathObject in pathObjects) {
					InstantiatePathObject (pathObject);
					yield return StartCoroutine (Sleep (waveItemDelayTime));
				}
				yield return StartCoroutine (Sleep (waveDelayTime));
				allowSwap = true;
			}
		}

		/// <summary>
		/// Instantiate the path object.
		/// </summary>
		/// <param name="pathObject">Path object component.</param>
		private void InstantiatePathObject (PathObject pathObject)
		{
			if (pathObject == null) {
				return;
			}

			GameObject newObject = Instantiate (pathObject.gameObject, Vector3.zero, Quaternion.identity) as GameObject;
			PathObject po = newObject.GetComponent<PathObject> ();
			newObject.name = pathObject.name;
			newObject.transform.SetParent (po.type == PathObject.Type.FLY ? GameManager.instance.flyObjectsParent : GameManager.instance.groundObjectsParent);
			newObject.transform.localScale = objectsScale;

			po.pathObjectMove.path = this;
			po.pathObjectMove.speed = objectsSpeed;
			po.pathObjectMove.MoveToStart ();
			po.SetAnimatorSpeed (objectsAnimatorSpeed);

			for (int i = 0; i < LevelsManager.instance.GetCurrentLevel ().targets.Count; i++) {
				//Note : Make sure every object has unique name
				if (LevelsManager.instance.GetCurrentLevel ().targets [i].pathObject.name == po.name) {
					po.isTarget = true;
					po.targetIndex = i;
					break;
				} 
			}

			if (trailEffect != null) {
				po.CreateTrailEffect (trailEffect);
			}

			Paths.instance.AddToPool (po);
		}

		/// <summary>
		/// Get the bezier points.
		/// </summary>
		/// <returns>The bezier list points.</returns>
		public List<Vector3> GetBezierPoints ()
		{
			return bezierPoints;
		}

		/// <summary>
		/// Get the count of bezier points.
		/// </summary>
		/// <returns>The bezier points count.</returns>
		public int GetBezierPointsCount ()
		{
			return bezierPoints.Count;
		}

		/// <summary>
		/// Calculate the bezier points.
		/// </summary>
		private void CalculateBezierPoints ()
		{
			bezierPoints.Clear ();

			//add not null points
			List<Transform> tempList = new List<Transform> ();
			for (int i = 0; i < points.Count; i++) {
				if (points [i] != null) {
					tempList.Add (points [i]);
				}
			}

			points = tempList;
			tempList = null;

			for (int i = 0; i < points.Count; i++) {
				bezierPoints.Add (points [i].position);
			}

			if (bezierPoints.Count > 1) {
				bezier.Interpolate (bezierPoints, 0.3f);
				bezierPoints = bezier.GetDrawingPoints2 ();
			}
		}

		/// <summary>
		/// Set the pause flag value.
		/// </summary>
		/// <param name="value">If set to <c>true</c> value.</param>
		public void SetPause (bool value)
		{
			this.isPaused = value;
		}

		/// <summary>
		/// On destroy the path event.
		/// </summary>
		void OnDestroy ()
		{
			StopAllCoroutines ();
		}

		/// <summary>
		/// The sleep coroutine.
		/// </summary>
		/// <param name="time">Sleep Time.</param>
		private IEnumerator Sleep (float time)
		{
			float count = 0;
			float delay = 0.01f;

			while(count < time){
				
				while(isPaused){
					yield return 0;
				}

				yield return new WaitForSeconds(delay);
				count += delay;
			}
		}

		public enum InstantiateMode
		{
			ONE_SHOT,
			LOOP
		};

		#if UNITY_EDITOR

		/* 
		 * This block of code works in the unity editor only
		*/

		public Color pathColor = Colors.transparent;

		public void CreateNewPoint (Vector3 pos)
		{
			GameObject newOb = new GameObject ("Point" + points.Count);
			points.Add (newOb.transform);
			newOb.transform.parent = transform;
			newOb.transform.position = pos;
			Selection.activeObject = newOb;
		}

		void OnDrawGizmos ()
		{
			CalculateBezierPoints ();
			DrawBezierCurve ();
		}

		private void DrawBezierCurve ()
		{
			if (pathColor == Colors.transparent) {
				pathColor = new Color (Random.Range (0, 255) / 255.0f, Random.Range (0, 255) / 255.0f, Random.Range (0, 255) / 255.0f, 1);
				DirtyUtil.MarkSceneDirty ();
			}

			for (int i = 0; i < bezierPoints.Count; i++) {

				for (int j = 0; j < points.Count; j++) {
					if (points [j].position == bezierPoints [i]) {

						if (i == 0 || i == bezierPoints.Count - 1) {
							Gizmos.color = Colors.greenColor;
						} else {
							Gizmos.color = pathColor;
						}
						if (!Application.isPlaying) {
							Gizmos.DrawWireSphere (bezierPoints [i], 0.25f);
						}
						break;
					}
				}
				Gizmos.color = pathColor;

				if (i + 1 < bezierPoints.Count) {
					Gizmos.DrawLine (bezierPoints [i], bezierPoints [i + 1]);
				}
			}
		}

		[MenuItem ("Tools/Bugs Bang Pack/Game Scene/New Path #p", false, 0)]
		static void CreateNewPathMenu ()
		{
			GameObject newPath = new GameObject ("Path");
			newPath.AddComponent<Path> ();
			GameObject pathParent = GameObject.Find ("Paths");
			if (pathParent == null) {
				pathParent = new GameObject ("Paths");
			}

			newPath.name = "Path" + (pathParent.transform.childCount + 1);

			newPath.transform.SetParent (pathParent.transform);

			newPath.transform.position = Vector3.zero;
			newPath.transform.localScale = Vector3.one;

			Selection.activeGameObject = newPath;
			DirtyUtil.MarkSceneDirty ();
		}

		[MenuItem ("Tools/Bugs Bang Pack/Game Scene/New Path #p", true, 0)]
		static bool CreateNewPathMenuValidate ()
		{
			return SceneManager.GetActiveScene ().name == "Game";
		}

		[MenuItem ("Tools/Bugs Bang Pack/Game Scene/Selected Path/New Point #c", false, 0)]
		static void CreateNewPointMenu ()
		{
			if (Selection.activeGameObject.GetComponent<Path> () != null) {
				Selection.activeGameObject.GetComponent<Path> ().CreateNewPoint (Vector3.zero);
			} else if (Selection.activeGameObject.GetComponentInParent<Path> () != null) {
				Selection.activeGameObject.GetComponentInParent<Path> ().CreateNewPoint (Vector3.zero);
			}

			DirtyUtil.MarkSceneDirty ();
		}

		[MenuItem ("Tools/Bugs Bang Pack/Game Scene/Selected Path/New Point #c", true, 0)]
		static bool CreateNewPointMenuValidate ()
		{
			bool PathFound = false;

			if (Selection.activeGameObject != null) {
				if (Selection.activeGameObject.GetComponent<Path> () != null || Selection.activeGameObject.GetComponentInParent<Path> () != null) {
					PathFound = true;
				}
			}
			return !Application.isPlaying && PathFound;
		}
		#endif
	}
}